import { PhysicsSystem2D } from "cc";
import SystemBase from "../../system/SystemBase";
import JNFrameSkeletonSystem from "./game/spine/JNFrameSkeletonSystem";
import JNSyncFrameComponent from "./game/JNSyncFrameComponent";
import RandomUtil from "../../util/RandomUtil";
import { EventDispatcher } from "../../util/EventDispatcher";
import JNFrameTweenSystem from "./game/tween/JNFrameTweenSystem";
import { EPhysics2DDrawFlags } from "cc";
import { find } from "cc";
import { sp } from "cc";
import JNFrameTime from "./game/time/JNFrameTime";
import { CCObject } from "cc";
import { game } from "cc";
import JMath from "../../util/JMath";

//通知
export enum JNSyncFrameEvent{
    CLEAR = "JNSyncFrameEvent_CLEAR", //清除世界
    CREATE = "JNSyncFrameEvent_CREATE", //创建世界
    FRAME = "JNSyncFrameEvent_FRAME", //每帧执行
}

//操作数据包
export interface JNFrameInput {
    //Id
    nId:number;
    //操作
    input:Uint8Array;
}

//操作列表
export interface JNFrameInputs{
    inputs:JNFrameInput[];
}

//数据包
export interface JNFrameInfo {
    index?:number;
    messages?:JNFrameInput[];
}

//数据包列表
export interface JNFrameInfos{
    frames:JNFrameInfo[]
}

//帧同步核心类
export default abstract class JNSyncFrame extends SystemBase {

    //物理
    instance: PhysicsSystem2D | null = null;

    //同步时间 (和服务器保持一致)
    nSyncTime:number = 67;
    //大于多少帧进行追帧
    nMaxFrameBan:number = 3;
    //大于多少帧进行快速追帧
    nMaxFrameLoopBan:number = 18;
    //将服务器帧数进行平分
    nDivideFrame:number = 3;

    //帧队列
    nFrameQueue:JNFrameInfo[] = [];
    //本地帧
    nLocalFrame:number = 0;
    //运行的帧
    nFrameRun:number = 0;

    get frame(){
        return this.nFrameRun;
    }

    get dt (){
        return this.nSyncTime / this.nDivideFrame / 1000;
    }

    //暂存帧列表
    nFrameTempQueue:Map<number,JNFrameInfo> = new Map;

    //帧更新
    dtTotal:number = 0;
    //输入更新
    dtInputTotal:number = 0;
    
    //需要同步的Actor
    nSyncActors:JNSyncFrameComponent<{}>[] = [];
    //ID 每添加 JNSyncFrameComponent + 1
    nSyncID:() => number = RandomUtil.Next(0);
    //初始化随机
    nRandSyncNumber:() => number = RandomUtil.SyncRandom(100);
    public SyncRandomInt(min:number,max:number):number{
        return RandomUtil.SyncRandomInt(this.nRandSyncNumber(),min,max);
    }

    //是否开始同步
    isStart:boolean = false;

    //是否请求服务器数据中
    isServerData:boolean = false;


    public async onInit() {

        //------------------- 重写 Math 工具 确保平台一致性 -----------------------
        console.log("JNSyncFrame 重写 Math 工具 确保平台一致性");
        Object.assign(Math, JMath);

        //通知清除
        EventDispatcher.getIns().emit(JNSyncFrameEvent.CLEAR);
        //即使摧毁
        CCObject._deferredDestroy();

        let that = this;

        //------------------- 重写 2D 物理 -----------------------
        Object.defineProperty(PhysicsSystem2D,"instance",{
            get(){
                if(!that.instance){

                    console.log("JNSyncFrame 初始化物理");
                    that.instance = new PhysicsSystem2D();
                    that.instance.enable = false;
                    that.instance.autoSimulation = false;
            
                    return that.instance;
                }else{
                    return that.instance;
                }
            }
        })
        PhysicsSystem2D["deleteInstance"] = () => {
            console.log("JNSyncFrame 销毁物理");
            that.instance = null;
        };

        PhysicsSystem2D.prototype.postUpdate = () => {}
        PhysicsSystem2D.prototype.update = () => {}

        try{
            sp.spine.MathUtils.randomTriangularWith = (min, max, mode) => {
                console.log("randomTriangularWith");
                var d = max - min;
                if (0.1 <= (mode - min) / d)
                    return min + Math.sqrt(0.1 * d * (mode - min));
                return max - Math.sqrt((1 - 0.1) * d * (max - mode));
            }
        }catch(e){}
        
        //摧毁Tween
        JNFrameTweenSystem.deleteInstance()
        //摧毁定时器
        JNFrameTime.deleteInstance()

        this.onResetValue();

        //通知创建
        EventDispatcher.getIns().emit(JNSyncFrameEvent.CREATE);
        this.onActionInit();
    }

    //开始
    public onStart(){
        this.isStart = true;
    }

    //销毁
    public onDestroy(){
        //通知清除
        EventDispatcher.getIns().emit(JNSyncFrameEvent.CLEAR);

        //销毁物理
        PhysicsSystem2D["deleteInstance"]();
        //销毁Spine
        JNFrameSkeletonSystem.deleteInstance();
        //摧毁Tween
        JNFrameTweenSystem.deleteInstance();
        //摧毁定时器
        JNFrameTime.deleteInstance()

        //及时摧毁
        CCObject._deferredDestroy();
        game.step();

        this.onResetValue();
        
    }

    //重置 JNSyncFrame
    public onReset(){
        //通知清除
        EventDispatcher.getIns().emit(JNSyncFrameEvent.CLEAR);

        //销毁物理
        PhysicsSystem2D["deleteInstance"]();
        //销毁Spine
        JNFrameSkeletonSystem.deleteInstance();
        //摧毁Tween
        JNFrameTweenSystem.deleteInstance();
        //摧毁定时器
        JNFrameTime.deleteInstance()

        //及时摧毁
        CCObject._deferredDestroy();
        game.step();

        this.onResetValue();

        //通知创建
        EventDispatcher.getIns().emit(JNSyncFrameEvent.CREATE);
        
        //及时摧毁
        CCObject._deferredDestroy();
        game.step();
        this.onActionInit();
    }

    //重置数据
    protected onResetValue(){
        this.isStart = false;
        this.nFrameQueue = [];
        this.nFrameTempQueue = new Map;
        this.nLocalFrame = 0;
        this.dtTotal = 0;
        this.nSyncActors = [];
        this.nSyncID = RandomUtil.Next(0);
        this.nRandSyncNumber = RandomUtil.SyncRandom(100);

        PhysicsSystem2D.instance.enable = false;
        PhysicsSystem2D.instance.autoSimulation = false;
    }

    //通知Action初始化完成
    protected onActionInit(){
        this.nSyncActors.forEach(action => {
            if(!action.isSyncInitSuccess){
                action.isSyncInitSuccess = true;
                action.onSyncInitSuccess();
            }
        })
    }

    //添加同步组件
    addSyncActor(sync:JNSyncFrameComponent<any>){
        if(this.nSyncActors.indexOf(sync) == -1){
            this.nSyncActors.push(sync);
        }
    }

    //销毁同步组件
    delSyncActor(sync:JNSyncFrameComponent<any>){
        this.nSyncActors.splice(this.nSyncActors.indexOf(sync),1);
    }

    //更新同步
    public update(dt:number){
        if(!this.isStart) return;

        this.dtTotal += dt*1000;
        this.dtInputTotal += dt*1000;

        let nSyncTime = this.dyTime();
        
        if(nSyncTime){
            while(nSyncTime != 0 && this.dtTotal > nSyncTime){
                this.onUpdate();
                this.dtTotal -= nSyncTime;
                nSyncTime = this.dyTime();
            }
        }else{
            //追帧运行 保持前端 15 帧 刷新
            let endTime = Date.now() + 66;
            while(this.dyTime() == 0 && Date.now() < endTime){
                this.onUpdate();
            }
        }

        //更新输入
        if(this.dtInputTotal > this.nSyncTime){
            this.dtInputTotal = 0;
            this.onUpdateInput();
        }

    }

    //更新输入
    private onUpdateInput(){

        //帧数据
        let inputs:JNFrameInput[] = [];

        //收集帧数据
        this.nSyncActors.forEach(action => {
            if(action.isInput()){
                //如果输入了
                inputs.push({
                    nId:action.nId,
                    input:action.encoder(action.input)
                });
            }
            //清空输入
            action.clearInput();
        })

        //发送输入
        inputs.length && this.onSendInput({
            inputs
        })

    }

    //获取帧数据
    abstract onServerData(start?:number,end?:number):Promise<JNFrameInfos>;

    //发送帧数据
    abstract onSendInput(message:JNFrameInputs);
    
    /**
     * 接收帧数据
     * @param frame 帧数据
     * @param isLatestData 是否最新的帧数据
     */
    addFrame(info:JNFrameInfo,isLatestData:boolean = false){

        info.index = info.index || 0;
        info.messages = info.messages || [];

        if(isLatestData){
            //如果推的帧小于本地帧 则 重开
            if(info.index < this.nLocalFrame){
                this.onReset();
                return;
            }
        }

        //我需要的下一帧
        let index:number = this.nLocalFrame + 1;

        //判断接受的帧是否下一帧 如果不是则加入未列入
        if(info.index != index){
            this.nFrameTempQueue[info.index] = info;

            //在未列入中拿到需要的帧
            let tamp = null;
            if(!(tamp = this.nFrameTempQueue[index])){
                //如果没有则向服务器请求我需要的帧数
                if(!this.isServerData){
                    this.isServerData = true;
                    //请求
                    this.onServerData(this.nLocalFrame).then(infos => {
                        //拿到数据 插入 帧列表
                        infos.frames.forEach(frame => {
                            if(frame.index > this.nLocalFrame){
                                this.addFrame(frame)
                            }
                        })
                        //结束
                        this.isServerData = false;
                    })
                }
                return;
            }else{
                info = tamp;
            }

        }

        //删除临时帧
        this.nFrameTempQueue.delete(info.index);

        this.nLocalFrame = index;

        //分帧插入
        this.nFrameQueue.push(info);
        for (let index = 0; index < this.nDivideFrame - 1; index++) {
            this.nFrameQueue.push({});            
        }

    }

    dyTime(){
        let dt:number = this.nSyncTime / this.nDivideFrame;
        let loop:number = dt;

        //大于nMaxFrameBan 进行 追帧
        if(this.nFrameQueue.length > this.nMaxFrameBan) {
                    
            //计算超过的帧数
            let exceed = this.nFrameQueue.length - this.nMaxFrameBan;
            let most = this.nMaxFrameLoopBan - this.nMaxFrameBan;
            let ldt = ((most - exceed) / most) * dt;
            
            //自适应追帧算法
            if(exceed <= this.nMaxFrameLoopBan){
                loop = ldt;
            }else{
                loop = 0;
            }
        }else{
            loop = dt;
        }
        return loop;
        // return dt;
    }

    private onUpdatePhysics(){
        let dt:number = this.nSyncTime / this.nDivideFrame / 1000;
        this.instance.physicsWorld.step(dt, 10, 10)
        this.instance.physicsWorld.syncPhysicsToScene();
        // this.instance.enable = true;
        // this.instance.autoSimulation = true;
        // this.instance.update(dt);
        // this.instance.enable = false;
        // this.instance.autoSimulation = false;

        // // if (this.instance.debugDrawFlags) {
        // this.instance.physicsWorld.drawDebug();

        // let canvas = find("Canvas");
        // let physics2dDebugDrawNode = canvas.getChildByName("PHYSICS_2D_DEBUG_DRAW");
        // if (physics2dDebugDrawNode) {
        //     physics2dDebugDrawNode.layer = 1;
        // }
        // // }
    }

    //运行帧
    protected onUpdate():JNFrameInfo{

        let frame:JNFrameInfo = this.nFrameQueue.shift();
        if(!frame) return;

        // if(frame.index == 115) debugger;

        let dt:number = this.nSyncTime / this.nDivideFrame;

        //拆出输入
        let inputs:{[key:number]:JNFrameInput} = {};
        (frame.messages || []).forEach((input) => {
            inputs[input.nId] = input;
        })

        // //通知初始化
        // this.onActionInit();

        //更新帧
        this.nSyncActors.forEach(action => {
            let input = inputs[action.nId];
            if(input){
                //解码
                action.onSyncUpdate(dt,frame,action.decoder(input.input))
                return;
            }
            action.onSyncUpdate(dt,frame);
        })

        //运行物理
        this.onUpdatePhysics();
        //运行Spine
        JNFrameSkeletonSystem.getInstance().onUpdate(dt / 1000);
        //更新Tween
        JNFrameTweenSystem.getInstance().onUpdate(dt);
        //更新定时器
        JNFrameTime.getInstance().onUpdate(dt);
        //即使摧毁
        CCObject._deferredDestroy();

        //通知初始化
        this.onActionInit();

        this.nFrameRun = frame.index || this.nFrameRun;

        //发送帧运行通知
        EventDispatcher.getIns().emit(JNSyncFrameEvent.FRAME);

        return frame;

    }

}

